﻿namespace LineGraphSample
{
    #region DelegateCommand
    #region Usings
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Threading;
    using System.Windows.Input;
    #endregion

    #region DelegateCommand

    public class DelegateCommand : DelegateCommandBase
    {
        private readonly Action _executeMethod;

        private Func<bool> _canExecuteMethod;

        public DelegateCommand(Action executeMethod)
            : this(executeMethod, () => true)
        {
        }

        public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
        {
            _executeMethod = executeMethod ?? throw new ArgumentNullException("executeMethod");
            _canExecuteMethod = canExecuteMethod;
        }

        protected override void Execute(object parameter)
        {
            Execute();
        }

        public void Execute()
        {
            _executeMethod();
        }

        protected override bool CanExecute(object parameter)
        {
            return CanExecute();
        }

        public bool CanExecute()
        {
            if (_canExecuteMethod != null)
            {
                return _canExecuteMethod();
            }
            return true;
        }

        public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
        {
            ObservesPropertyInternal(propertyExpression);
            return this;
        }

        public DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
        {
            _canExecuteMethod = canExecuteExpression.Compile();
            ObservesPropertyInternal(canExecuteExpression);
            return this;
        }
    }
    #endregion

    #region DelegateCommand<T>
    public class DelegateCommand<T> : DelegateCommandBase
    {
        private readonly Action<T> _executeMethod;

        private Func<T, bool> _canExecuteMethod;

        public DelegateCommand(Action<T> executeMethod)
            : this(executeMethod, (Func<T, bool>)((T o) => true))
        {
        }

        public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
        {
            if (executeMethod == null)
            {
                throw new ArgumentNullException("executeMethod");
            }
            TypeInfo typeInfo = typeof(T).GetTypeInfo();
            if (typeInfo.IsValueType && (!typeInfo.IsGenericType || !typeof(Nullable<>).GetTypeInfo().IsAssignableFrom(typeInfo.GetGenericTypeDefinition().GetTypeInfo())))
            {
                throw new InvalidCastException("无效的 DelegateCommand<T> 泛型类型 " + typeInfo.Name);
            }
            _executeMethod = executeMethod;
            _canExecuteMethod = canExecuteMethod;
        }

        protected override void Execute(object parameter)
        {
            Execute((T)parameter);
        }

        public void Execute(T parameter)
        {
            _executeMethod(parameter);
        }

        protected override bool CanExecute(object parameter)
        {
            return CanExecute((T)parameter);
        }

        public bool CanExecute(T parameter)
        {
            return _canExecuteMethod(parameter);
        }

        public DelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> propertyExpression)
        {
            ObservesPropertyInternal(propertyExpression);
            return this;
        }

        public DelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
        {
            Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(canExecuteExpression.Body, new ParameterExpression[1] { Expression.Parameter(typeof(T), "o") });
            _canExecuteMethod = expression.Compile();
            ObservesPropertyInternal(canExecuteExpression);
            return this;
        }
    }

    #endregion

    #region DelegateCommandBase
    public abstract class DelegateCommandBase : ICommand
    {
        private readonly SynchronizationContext _synchronizationContext;

        private readonly HashSet<string> _observedPropertiesExpressions = new HashSet<string>();

        private bool _isActive;

        public bool IsActive
        {
            get
            {
                return _isActive;
            }
            set
            {
                if (_isActive != value)
                {
                    _isActive = value;
                    OnIsActiveChanged();
                }
            }
        }

        public virtual event EventHandler CanExecuteChanged;

        public virtual event EventHandler IsActiveChanged;

        protected DelegateCommandBase()
        {
            _synchronizationContext = SynchronizationContext.Current;
        }

        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute(parameter);
        }

        protected abstract bool CanExecute(object parameter);

        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged();
        }

        protected virtual void OnCanExecuteChanged()
        {
            EventHandler handler = this.CanExecuteChanged;
            if (handler == null)
            {
                return;
            }
            if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
            {
                _synchronizationContext.Post(delegate
                {
                    handler(this, EventArgs.Empty);
                }, null);
            }
            else
            {
                handler(this, EventArgs.Empty);
            }
        }

        protected internal void ObservesPropertyInternal<T>(Expression<Func<T>> propertyExpression)
        {
            if (_observedPropertiesExpressions.Contains(propertyExpression.ToString()))
            {
                throw new ArgumentException($"{propertyExpression} is already being observed.", "propertyExpression");
            }
            _observedPropertiesExpressions.Add(propertyExpression.ToString());
            PropertyObserver.Observes(propertyExpression, RaiseCanExecuteChanged);
        }

        void ICommand.Execute(object parameter)
        {
            Execute(parameter);
        }

        protected abstract void Execute(object parameter);

        protected virtual void OnIsActiveChanged()
        {
            this.IsActiveChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    #endregion

    #region PropertyObserver
    internal class PropertyObserver
    {
        private readonly Action _action;

        private PropertyObserver(Expression propertyExpression, Action action)
        {
            _action = action;
            SubscribeListeners(propertyExpression);
        }

        private void SubscribeListeners(Expression propertyExpression)
        {
            Stack<PropertyInfo> stack = new Stack<PropertyInfo>();
            while (propertyExpression is MemberExpression memberExpression)
            {
                propertyExpression = memberExpression.Expression;
                stack.Push(memberExpression.Member as PropertyInfo);
            }
            if (!(propertyExpression is ConstantExpression constantExpression))
            {
                throw new NotSupportedException("Operation not supported for the given expression type. Only MemberExpression and ConstantExpression are currently supported.");
            }
            PropertyObserverNode propertyObserverNode = new PropertyObserverNode(stack.Pop(), _action);
            PropertyObserverNode propertyObserverNode2 = propertyObserverNode;
            using (Stack<PropertyInfo>.Enumerator enumerator = stack.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    PropertyObserverNode propertyObserverNode4 = (propertyObserverNode2.Next = new PropertyObserverNode(enumerator.Current, _action));
                    propertyObserverNode2 = propertyObserverNode4;
                }
            }
            if (!(constantExpression.Value is INotifyPropertyChanged inpcObject))
            {
                throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that owns '" + propertyObserverNode.PropertyInfo.Name + "' property, but the object does not implements INotifyPropertyChanged.");
            }
            propertyObserverNode.SubscribeListenerFor(inpcObject);
        }

        internal static PropertyObserver Observes<T>(Expression<Func<T>> propertyExpression, Action action)
        {
            return new PropertyObserver(propertyExpression.Body, action);
        }
    }
    #endregion

    #region PropertyObserverNode
    internal class PropertyObserverNode
    {
        private readonly Action _action;

        private INotifyPropertyChanged _inpcObject;

        public PropertyInfo PropertyInfo { get; }

        public PropertyObserverNode Next { get; set; }

        public PropertyObserverNode(PropertyInfo propertyInfo, Action action)
        {
            PropertyObserverNode propertyObserverNode = this;
            PropertyInfo = propertyInfo ?? throw new ArgumentNullException("propertyInfo");
            _action = delegate
            {
                action?.Invoke();
                if (propertyObserverNode.Next != null)
                {
                    propertyObserverNode.Next.UnsubscribeListener();
                    propertyObserverNode.GenerateNextNode();
                }
            };
        }

        public void SubscribeListenerFor(INotifyPropertyChanged inpcObject)
        {
            _inpcObject = inpcObject;
            _inpcObject.PropertyChanged += OnPropertyChanged;
            if (Next != null)
            {
                GenerateNextNode();
            }
        }

        private void GenerateNextNode()
        {
            object value = PropertyInfo.GetValue(_inpcObject);
            if (value != null)
            {
                if (!(value is INotifyPropertyChanged inpcObject))
                {
                    throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that owns '" + Next.PropertyInfo.Name + "' property, but the object does not implements INotifyPropertyChanged.");
                }
                Next.SubscribeListenerFor(inpcObject);
            }
        }

        private void UnsubscribeListener()
        {
            if (_inpcObject != null)
            {
                _inpcObject.PropertyChanged -= OnPropertyChanged;
            }
            Next?.UnsubscribeListener();
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e?.PropertyName == PropertyInfo.Name || e == null || e.PropertyName == null)
            {
                _action?.Invoke();
            }
        }
    }
    #endregion

    #endregion
}
